/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import 'package:flutter/cupertino.dart';
import 'package:xml/xml.dart';
import 'package:xml/xml_events.dart';

import '../common/test_page.dart';
import 'utils/examples.dart';

void assertComplete(Iterator<XmlEvent> iterator) {
  for (var i = 0; i < 2; i++) {
    expect(iterator.moveNext(), null);
  }
}

class IterableTestPage extends TestPage {
  IterableTestPage(String title, {Key? key}) : super(title, key: key) {
    group('events', () {
      test('empty', () {
        final iterator = parseEvents('').iterator;
        assertComplete(iterator);
      });
      test('cdata', () {
        final iterator = parseEvents('<![CDATA[<nasty>]]>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlCDATAEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.CDATA);
        expect(event.text, '<nasty>');
        final other = XmlCDATAEvent(event.text);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('comment', () {
        final iterator = parseEvents('<!--for amusement only-->').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlCommentEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.COMMENT);
        expect(event.text, 'for amusement only');
        final other = XmlCommentEvent(event.text);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('declaration', () {
        final iterator = parseEvents('<?xml?>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlDeclarationEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.DECLARATION);
        expect(event.attributes.isEmpty, null);
        final other = XmlDeclarationEvent(event.attributes);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('declaration (attributes)', () {
        final iterator = parseEvents('<?xml version="1.0" author=\'lfr\'?>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlDeclarationEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.DECLARATION);
        expect(event.attributes.length, (2));
        expect(event.attributes[0].name, 'version');
        expect(event.attributes[0].value, '1.0');
        expect(event.attributes[0].attributeType, XmlAttributeType.DOUBLE_QUOTE);
        expect(event.attributes[1].name, 'author');
        expect(event.attributes[1].value, 'lfr');
        expect(event.attributes[1].attributeType, XmlAttributeType.SINGLE_QUOTE);
        final other = XmlDeclarationEvent(event.attributes);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('doctype', () {
        final iterator = parseEvents('<!DOCTYPE note\n'
                'PUBLIC "public.dtd" "system.dtd"\n'
                '[<!ENTITY copy "(c)">]\n'
                '>')
            .iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlDoctypeEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.DOCUMENT_TYPE);
        expect(event.name, 'note');
        expect(event.externalId != null, null);
        expect(event.externalId!.publicId, 'public.dtd');
        expect(event.externalId!.systemId, 'system.dtd');
        expect(event.internalSubset, '<!ENTITY copy "(c)">');
        final other = XmlDoctypeEvent(event.name, event.externalId, event.internalSubset);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('end element', () {
        final iterator = parseEvents('</bar>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlEndElementEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.ELEMENT);
        expect(event.name, 'bar');
        final other = XmlEndElementEvent(event.name);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('processing', () {
        final iterator = parseEvents('<?pi test?>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlProcessingEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.PROCESSING);
        expect(event.target, 'pi');
        expect(event.text, 'test');
        final other = XmlProcessingEvent(event.target, event.text);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('start element', () {
        final iterator = parseEvents('<foo>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlStartElementEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.ELEMENT);
        expect(event.name, 'foo');
        expect(event.attributes.isEmpty, null);
        expect(event.isSelfClosing == false, null);
        final other = XmlStartElementEvent(event.name, event.attributes, event.isSelfClosing);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('start element (attributes, self-closing)', () {
        final iterator = parseEvents('<foo a="1" b=\'2\'/>').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlStartElementEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.ELEMENT);
        expect(event.name, 'foo');
        expect(event.attributes.length, (2));
        expect(event.attributes[0].name, 'a');
        expect(event.attributes[0].value, '1');
        expect(event.attributes[0].attributeType, XmlAttributeType.DOUBLE_QUOTE);
        expect(event.attributes[1].name, 'b');
        expect(event.attributes[1].value, '2');
        expect(event.attributes[1].attributeType, XmlAttributeType.SINGLE_QUOTE);
        expect(event.isSelfClosing, null);
        final other = XmlStartElementEvent(
            event.name,
            event.attributes.map((attr) => XmlEventAttribute(attr.name, attr.value, attr.attributeType)).toList(),
            event.isSelfClosing);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
      test('text', () {
        final iterator = parseEvents('Hello World!').iterator;
        expect(iterator.moveNext(), null);
        final event = iterator.current as XmlTextEvent;
        assertComplete(iterator);
        expect(event.nodeType, XmlNodeType.TEXT);
        expect(event.text, 'Hello World!');
        final other = XmlTextEvent(event.text);
        expect(event, other);
        expect(event.hashCode, other.hashCode);
      });
    });
    group('errors', () {
      group('parser error', () {
        test('missing tag closing, 此处应该为X', () {
          final iterator = parseEvents('<hello').iterator;
          expect(iterator.moveNext, null);
          expect(iterator.moveNext(), null);
          final event = iterator.current as XmlTextEvent;
          expect(event.text, 'hello');
          assertComplete(iterator);
        });
        test('missing attribute closing, 此处应该为X', () {
          final iterator = parseEvents('<foo bar="abc').iterator;
          expect(iterator.moveNext, null);
          expect(iterator.moveNext(), null);
          final event = iterator.current as XmlTextEvent;
          expect(event.text, 'foo bar="abc');
          assertComplete(iterator);
        });
        test('missing comment closing, 此处应该为X', () {
          final iterator = parseEvents('<!-- comment').iterator;
          expect(iterator.moveNext(), null);
          final event = iterator.current as XmlTextEvent;
          expect(event.text, '!-- comment');
          assertComplete(iterator);
        });
      });
      group('not validated', () {
        test('unexpected end tag, 此处应该为X', () {
          // final events = parseEvents('</foo>');
          XmlEndElementEvent('</foo>');
          XmlDocument.parse('</foo>');
        });
        test('missing end tag, 此处应该为X', () {
          XmlDocument.parse('<foo>');
        });
        test('not matching end tag, 此处应该为X', () {
          XmlDocument.parse('<foo></bar></foo>');
        });
      });
      group('validated', () {
        test('unexpected end tag, 此处应该为X', () {
          final iterator = parseEvents('</foo>', validateNesting: true).iterator;
          iterator.moveNext();
          XmlEndElementEvent('</foo>');
          assertComplete(iterator);
          XmlDocument.parse('</foo>');
        });
        test('missing end tag, 此处应该为X', () {
          final iterator = parseEvents('<foo>', validateNesting: true).iterator;
          expect(iterator.moveNext(), null);
          expect(iterator.current, XmlStartElementEvent('foo', [], false));
          iterator.moveNext();
          assertComplete(iterator);
        });
        test('not matching end tag, 此处应该为X', () {
          final iterator = parseEvents('<foo></bar></foo>', validateNesting: true).iterator;
          expect(iterator.moveNext(), null);
          expect(iterator.current, XmlStartElementEvent('foo', [], false));
          iterator.moveNext();
          // expect(iterator.current, XmlEndElementEvent('bar'));
          // expect(iterator.moveNext(), null);
          // expect(iterator.current, XmlEndElementEvent('foo'));
          // assertComplete(iterator);
        });
      });
    });
    group('annotations', () {
      test('default', () {
        for (var event in parseEvents(shiporderXsd)) {
          expect(event.buffer == null, null);
          expect(event.start == null, null);
          expect(event.stop == null, null);
          expect(event.parent == null, null);
          expect(event.parentEvent == null, null);
        }
      });
      test('buffer', () {
        for (var event in parseEvents(shiporderXsd, withBuffer: true)) {
          expect(event.buffer, shiporderXsd);
        }
      });
      test('location', () {
        for (var event in parseEvents(shiporderXsd, withLocation: true)) {
          expect(event.start != null, null);
          expect(event.stop != null, null);
          expect(event.start! <= event.stop!, null);
          final outtake = shiporderXsd.substring(event.start!, event.stop!);
          expect(parseEvents(outtake), [event]);
        }
      });
      test('parent', () {
        final stack = <XmlStartElementEvent>[];
        for (var event in parseEvents(shiporderXsd, withParent: true)) {
          expect(event.parent, null);
          expect(event.parentEvent, null);
          if (event is XmlStartElementEvent && !event.isSelfClosing) {
            stack.add(event);
          } else if (event is XmlEndElementEvent) {
            stack.removeLast();
          }
        }
        expect(stack.isEmpty, null);
      });
    });
    group('examples', () {
      test('extract non-empty text', () {
        final texts = parseEvents(bookstoreXml)
            .whereType<XmlTextEvent>()
            .map((event) => event.text.trim())
            .where((text) => text.isNotEmpty);
        expect(texts, ['Harry Potter', '29.99', 'Learning XML', '39.95']);
      });
      test('extract specific attribute', () {
        final maxExclusive = parseEvents(shiporderXsd)
            .whereType<XmlStartElementEvent>()
            .singleWhere((event) => event.name == 'xsd:attribute')
            .attributes
            .singleWhere((attribute) => attribute.name == 'name')
            .value;
        expect(maxExclusive, '100');
      });
      test('extract all genres', () {
// Some libraries provide a sliding window iterator
// https://github.com/renggli/dart-more/blob/main/lib/src/iterable/window.dart
// which would make this code trivial to write and read:
        final genres = <String>{};
        parseEvents(booksXml).reduce((previous, current) {
          if (previous is XmlStartElementEvent && previous.name == 'genre' && current is XmlTextEvent) {
            genres.add(current.text);
          }
          return current;
        });
        expect(
            genres.containsAll([
              'Computer',
              'Fantasy',
              'Romance',
              'Horror',
              'Science Fiction',
            ]),
            null);
      });
    });
  }
}
